﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Psm.Messaging;
using Psm.Transport;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using Psm.Logging;
using Psm.Container;
using Psm.Util;


namespace Psm.Protocol.FrameProtocol
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export("FRAME_PROTOCOL",typeof(IProtocol))]
    class FrameProtocol : IProtocol
    {
        private static ILogger logger = Repository.GetInstance<ILogger>();

        private IClientTransport clientTransport;
        private IMessageFactory messageFactory;
        public event EventHandler<ProtocolShutdownEventArgs> ProtocolShutdown;
        private ByteBuffer currentBuffer = new ByteBuffer();
        private AsyncQueue<ByteBuffer> parsedBufferQ;

        public void InitializeProtocol(IClientTransport clientTransport, IMessageFactory messageFactory)
        {
            parsedBufferQ = new AsyncQueue<ByteBuffer>(new System.Threading.WaitCallback(ParseMessageHandler));
            this.clientTransport = clientTransport;
            this.messageFactory = messageFactory;
            this.clientTransport.TransportClosed += new EventHandler<TransportClosedEventArgs>((o, e) => TransportClosedHandler(o,e));
        }

        private void TransportClosedHandler(object o, TransportClosedEventArgs e)
        {
            OnProtocolShutdown();
        }

        private void OnProtocolShutdown()
        {
            EventHandler<ProtocolShutdownEventArgs> handler = this.ProtocolShutdown;
            if (handler != null)
                handler(this, new ProtocolShutdownEventArgs());
        }

        public void SendMessage(IMessage message)
        {   
            try
            {
                lock (this)
                {
                    ByteBuffer.BufferState state = currentBuffer.GetState();
                    currentBuffer.Pad(4);
                    bool serialized = message.Serialize(currentBuffer);
                    if (!serialized)
                    {
                        currentBuffer.RestoreState(state);
                        clientTransport.SendAsync(currentBuffer);

                        currentBuffer = new ByteBuffer();
                        state = currentBuffer.GetState();
                        currentBuffer.Pad(4);
                        serialized = message.Serialize(currentBuffer); //TODO:assumed message is alwasy shorter then default buffer size
                        currentBuffer.Write(state.Position, (currentBuffer.CurrentIndex - state.Position) - 4);
                    }
                    else
                    {
                        currentBuffer.Write(state.Position, (currentBuffer.CurrentIndex - state.Position) - 4);
                    }
                }
                
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        public event EventHandler<NewMessageEventArgs> NewMessage;

        public void StartProcessing()
        {
            try
            {
                clientTransport.BeginReceive(new ByteBuffer(), new Action<IClientTransport>(this.RceiveBuffer));
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        public void RceiveBuffer(IClientTransport transport)
        {
            try
            {
                ByteBuffer buffer = clientTransport.EndReceive();
                buffer = ParseBuffer(buffer);
                clientTransport.BeginReceive(buffer, new Action<IClientTransport>(this.RceiveBuffer));
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }

        private void OnNewMessage(IMessage message)
        {
            if( this.NewMessage != null )
            {   
                NewMessage(this,new NewMessageEventArgs(message));
            }
        }

        public void Flush()
        {
            if (currentBuffer.Length > 0)
            {
                clientTransport.SendAsync(currentBuffer);
                currentBuffer = new ByteBuffer();
            }
        }

        private ByteBuffer ParseBuffer(ByteBuffer buffer)
        {
            if (buffer.Length > 0)
            {
                int unparsePosition = buffer.StartIndex;
                byte[] bytes = buffer.GetBuffer();
                while (unparsePosition <= buffer.EndIndex)
                {
                    if (unparsePosition + 3 <= buffer.EndIndex)
                    {
                        int messagelength = BitConverter.ToInt32(bytes, unparsePosition);
                        if (unparsePosition + messagelength + 3 > buffer.EndIndex)
                        {
                            //incomplete message
                            ByteBuffer newBuffer = new ByteBuffer();
                            newBuffer.Write(bytes, unparsePosition, buffer.EndIndex - unparsePosition + 1);
                            return newBuffer;
                        }
                        else
                        {
                            //full message present
                            ByteBuffer messageBuffer = new ByteBuffer(bytes, unparsePosition + 4, unparsePosition + 4 + messagelength, messagelength);
                            parsedBufferQ.Enqueue(messageBuffer);
                            //ByteBuffer messageBuffer = (ByteBuffer)obj;
                            //IMessage message = messageFactory.CreateMessage(messageBuffer);
                            //OnNewMessage(message);
                            unparsePosition = unparsePosition + sizeof(int) + messagelength;
                        }
                    }
                    else
                    {
                        //incomplete length
                        ByteBuffer newBuffer = new ByteBuffer();
                        newBuffer.Write(bytes, unparsePosition, buffer.EndIndex - unparsePosition + 1);
                        return newBuffer;
                    }
                }
            }
            return new ByteBuffer();
        }

        private void ParseMessageHandler(object obj)
        {
            try
            {
                ByteBuffer messageBuffer = (ByteBuffer)obj;
                IMessage message = messageFactory.CreateMessage(messageBuffer);
                OnNewMessage(message);
            }
            catch (Exception ex)
            {
                logger.Exception(ex);
            }
        }
    }
}
